package udpserver;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.logging.Level;
import java.util.logging.Logger;
import libudp.Ack;
import libudp.Serializer;
import libudp.Syn;

/**
 * The UDPServer class is a socket-based, datagram server. This class opens a
 * new socket on a specified port and listens for incoming datagram packets.
 * 
 * UDPServer maintains an internal sequence number counter. Packets are
 * numbered. When a packet is received, UDPServer will increment its internal 
 * sequence number counter and request another packet with the next sequence
 * number.
 * @author davidmessing
 */
public class UDPServer extends Thread {
    
    protected DatagramSocket socket = null; // socket to open connection  
    private byte[] buf; // byte array to receive packet data
    private DatagramPacket packet = null;   // packet to receive and send on socket
    private int sequenceNum = -1;    // keep track of packet numbers
    
    /**
     * Public constructor for UDPServer.
     * @throws SocketException
     */
    public UDPServer() throws SocketException {
        this("UDPServer", 6500);
    }
    
    /**
     * Public constructor for UDPServer. Name defines the thread name.
     * @param name
     * @throws SocketException
     */
    public UDPServer(String name, int port) throws SocketException {
        super(name);
        socket = new DatagramSocket(port);  // open a new socket
    }
    
    private Syn convertByteToSyn(byte[] buffer) {
        Syn s = null;  
        try {
            s = (Syn) Serializer.deserialize(buffer); // deserialize byte[] to Syn object
        } catch (IOException | ClassNotFoundException ex) {
            Logger.getLogger(UDPServer.class.getName()).log(Level.SEVERE, null, ex);
        }
        return s;
    }
    
    @Override
    public void run() { 
        do {
            try {
                buf = new byte[256];
                packet = new DatagramPacket(buf, buf.length);    // create DatagramPacket            
                socket.receive(packet); // recieve request
                long timeReceived = System.currentTimeMillis();

                // convert datagram packet data to Syn object
                Syn s = convertByteToSyn(packet.getData());  // deserialize byte[] to Syn object                                
                if (s.getSequenceNum() > sequenceNum) {
                    System.out.println("UDPServer: ts=" + timeReceived + ", tc1=" + s.getTime() + ", sequenceNumberReceived=" + s.getSequenceNum() + ", message=" + s.getMessage());
                    //System.out.println("UDPServer Received: " + s); // print Syn object
                    sequenceNum = s.getSequenceNum();   // record sequence number of Syn
                }                
                
                // convert Ack to byte[]
                Ack a = new Ack(s.getTime(), timeReceived, (sequenceNum+1), "Ack");  // create Ack to send
                buf = Serializer.serialize(a);    // convert Ack to byte[]
                
                // create a datagram packet to return to the client
                InetAddress address = packet.getAddress();  // get return address from received DatagramPacket
                int clientPort = packet.getPort();    //  get return port from received DatagramPacket
                packet = new DatagramPacket(buf, buf.length, address, clientPort);    // init DatagramPacket
                socket.send(packet);    // send packet
                                
            } catch (IOException ex) {
                Logger.getLogger(UDPServer.class.getName()).log(Level.SEVERE, null, ex);
            }            
        } while (sequenceNum < 100); // terminate the program after 1000 packets
          
        socket.close(); // close the socket       
    }

    /**
     * @param args the command line arguments
     * @throws SocketException  
     */
    public static void main(String[] args) throws SocketException {
        if (args.length == 1) {
            System.out.println("Attempting to open socket on port  " + args[0] + "...");
            new UDPServer("UDPServer", Integer.parseInt(args[0])).start();
        }
        else {
            new UDPServer().start();    // run with default args (localhost)
        }
        
        System.out.print(args);
    }
}
